//
// Copyright © 2014, David Tesler (https://github.com/protobufel)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the <organization> nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
package com.github.protobufel.grammar;
import static com.github.protobufel.grammar.PrimitiveTypesUtil.protoEscapeBytes;
import static com.github.protobufel.grammar.PrimitiveTypesUtil.unescapeBytes;
import static com.github.protobufel.grammar.PrimitiveTypesUtil.unescapeText;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.RuleNode;
import com.github.protobufel.grammar.ErrorListeners.ConsoleProtoErrorListener;
import com.github.protobufel.grammar.ErrorListeners.IBaseProtoErrorListener;
import com.github.protobufel.grammar.ErrorListeners.IProtoErrorListener;
import com.github.protobufel.grammar.Exceptions.FieldInExtensionRangeException;
import com.github.protobufel.grammar.Exceptions.InvalidExtensionRange;
import com.github.protobufel.grammar.Exceptions.NonUniqueException;
import com.github.protobufel.grammar.Exceptions.NonUniqueExtensionNumber;
import com.github.protobufel.grammar.Exceptions.UnresolvedTypeNameException;
import com.github.protobufel.grammar.PrimitiveTypesUtil.InvalidEscapeSequenceException;
import com.github.protobufel.grammar.ProtoParser.BoolFieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.BytesFieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.CustomOptionContext;
import com.github.protobufel.grammar.ProtoParser.CustomOptionNamePartContext;
import com.github.protobufel.grammar.ProtoParser.DoubleFieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.EnumDefaultFieldContext;
import com.github.protobufel.grammar.ProtoParser.EnumDefaultFieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.EnumFieldContext;
import com.github.protobufel.grammar.ProtoParser.EnumStatementContext;
import com.github.protobufel.grammar.ProtoParser.ExtendContext;
import com.github.protobufel.grammar.ProtoParser.ExtensionRangeEndContext;
import com.github.protobufel.grammar.ProtoParser.ExtensionsContext;
import com.github.protobufel.grammar.ProtoParser.FieldContext;
import com.github.protobufel.grammar.ProtoParser.FieldNumberContext;
import com.github.protobufel.grammar.ProtoParser.Fixed32FieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.Fixed64FieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.FloatFieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.GroupContext;
import com.github.protobufel.grammar.ProtoParser.Int32FieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.Int64FieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.MessageContext;
import com.github.protobufel.grammar.ProtoParser.MethodStatementContext;
import com.github.protobufel.grammar.ProtoParser.OneofFieldContext;
import com.github.protobufel.grammar.ProtoParser.OneofGroupContext;
import com.github.protobufel.grammar.ProtoParser.OneofStatementContext;
import com.github.protobufel.grammar.ProtoParser.OptionAggregateValueContext;
import com.github.protobufel.grammar.ProtoParser.OptionAggregateValueFieldContext;
import com.github.protobufel.grammar.ProtoParser.OptionScalarValueContext;
import com.github.protobufel.grammar.ProtoParser.OptionalScalarFieldContext;
import com.github.protobufel.grammar.ProtoParser.ProtoContext;
import com.github.protobufel.grammar.ProtoParser.PublicImportContext;
import com.github.protobufel.grammar.ProtoParser.RegularImportContext;
import com.github.protobufel.grammar.ProtoParser.ServiceContext;
import com.github.protobufel.grammar.ProtoParser.Sfixed32FieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.Sfixed64FieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.Sint32FieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.Sint64FieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.StandardEnumOptionAllowAliasContext;
import com.github.protobufel.grammar.ProtoParser.StandardEnumOptionDeprecatedContext;
import com.github.protobufel.grammar.ProtoParser.StandardEnumValueOptionDeprecatedContext;
import com.github.protobufel.grammar.ProtoParser.StandardFieldOptionCTypeOptionContext;
import com.github.protobufel.grammar.ProtoParser.StandardFieldOptionDeprecatedContext;
import com.github.protobufel.grammar.ProtoParser.StandardFieldOptionExperimentalMapKeyContext;
import com.github.protobufel.grammar.ProtoParser.StandardFieldOptionLazyContext;
import com.github.protobufel.grammar.ProtoParser.StandardFieldOptionPackedContext;
import com.github.protobufel.grammar.ProtoParser.StandardFieldOptionWeakContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionCcGenericServicesContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionDeprecatedContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionGoPackageContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionJavaGenerateEqualsAndHashContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionJavaGenericServicesContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionJavaMultipleFilesContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionJavaOuterClassnameContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionJavaPackageContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionJavaStringCheckUtf8Context;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionOptimizeForContext;
import com.github.protobufel.grammar.ProtoParser.StandardFileOptionPyGenericServicesContext;
import com.github.protobufel.grammar.ProtoParser.StandardMessageOptionDeprecatedContext;
import com.github.protobufel.grammar.ProtoParser.StandardMessageOptionMessageSetWireFormatContext;
import com.github.protobufel.grammar.ProtoParser.StandardMessageOptionNoStandardDescriptorAccessorContext;
import com.github.protobufel.grammar.ProtoParser.StandardMethodOptionDeprecatedContext;
import com.github.protobufel.grammar.ProtoParser.StandardServiceOptionDeprecatedContext;
import com.github.protobufel.grammar.ProtoParser.StringFieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.Uint32FieldOptionContext;
import com.github.protobufel.grammar.ProtoParser.Uint64FieldOptionContext;
import com.github.protobufel.grammar.SymbolScopes.NameContext;
import com.google.protobuf.ByteString;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.DescriptorProtos.DescriptorProto.ExtensionRange;
import com.google.protobuf.DescriptorProtos.EnumValueDescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto.Type;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProtoOrBuilder;
import com.google.protobuf.DescriptorProtos.FieldOptions;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileDescriptorProtoOrBuilder;
import com.google.protobuf.DescriptorProtos.FileOptions.OptimizeMode;
import com.google.protobuf.DescriptorProtos.MethodDescriptorProto;
import com.google.protobuf.DescriptorProtos.OneofDescriptorProto;
import com.google.protobuf.DescriptorProtos.OneofDescriptorProtoOrBuilder;
import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto;
import com.google.protobuf.DescriptorProtos.UninterpretedOption;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
/**
* A parser from .proto into FileDescriptorProto.
*
* @author protobufel@gmail.com David Tesler
*/
class ProtoFileParser extends ProtoBaseListener {
private static final String AGREGATE_VALUE_FIELD_DELIMITER = " ";
// private static final int UNINTERPRETED_OPTION_FIELD_NUMBER = 999;
public static final int MAX_FIELD_NUMBER = 536870911;
protected final FileDescriptorProto.Builder fileBuilder;
// protected SourceCodeInfo.Builder sourceBuilder;
private final Scopes scopes;
// the context reverse lookup by its protoBuilder, mostly needed for fields validations
private final ContextLookup contextLookup;
// The big question is whether to enforce uniqueness constraints in the parser
// as much as possible, or defer it to FieldDescriptor.buildFrom() method; and
// whether this method does all the checks!
public ProtoFileParser(final String protoName) {
this(protoName, new ConsoleProtoErrorListener(protoName));
}
public ProtoFileParser(final String protoName, final IBaseProtoErrorListener errorListener) {
fileBuilder = FileDescriptorProto.newBuilder().setName(protoName);
errorListener.setProtoName(protoName);
contextLookup = new ContextLookup(errorListener);
scopes = new Scopes(fileBuilder, contextLookup);
// this.sourceBuilder = fileBuilder.getSourceCodeInfoBuilder();
}
public IProtoErrorListener getErrorListener() {
return contextLookup.getErrorListener();
}
@Override
public void enterProto(final ProtoContext ctx) {
if (ctx.packageStatement() != null) {
fileBuilder.setPackage(ctx.packageStatement().packageName().getText());
}
scopes.init();
}
public ParsedContext getParsedWithResolution() {
// resolve what it can, next - during linking!
// FIXME report errors early if there are no dependencies, instead of waiting for linking!
// FIXME do nothing if there is a public dependency?
final List<FieldDescriptorProto.Builder> unresolved = scopes.resolveAllSymbols();
// only local validation here, as per protoc - other protos might clash with these!
scopes.validateAllExtensionNumbers();
return new ParsedContext(fileBuilder, unresolved, contextLookup);
}
public ParsedContext getParsed() {
return new ParsedContext(fileBuilder, null, contextLookup);
}
@Override
public void exitRegularImport(final RegularImportContext ctx) {
fileBuilder.addDependency(removeQuotes(ctx.importPath().getText()));
}
@Override
public void exitPublicImport(final PublicImportContext ctx) {
fileBuilder.addDependency(removeQuotes(ctx.importPath().getText()));
fileBuilder.addPublicDependency(fileBuilder.getDependencyCount() - 1);
// FIXME everything after import public should really be ignored!
}
// @Override
// public void exitPackageStatement(PackageStatementContext ctx) {
// fileBuilder.setPackage(ctx.packageName().getText());
// }
// ****************************** All Options START ************************************
// **** FileOptions
@Override
public void exitStandardFileOptionJavaPackage(final StandardFileOptionJavaPackageContext ctx) {
verifyOptionNameUnique("javaPackage", ctx.getStart());
scopes.getFileOptions().setJavaPackage(removeQuotes(ctx.StringLiteral().getText()));
}
@Override
public void exitStandardFileOptionGoPackage(final StandardFileOptionGoPackageContext ctx) {
verifyOptionNameUnique("goPackage", ctx.getStart());
scopes.getFileOptions().setGoPackage(removeQuotes(ctx.StringLiteral().getText()));
}
@Override
public void exitStandardFileOptionJavaOuterClassname(
final StandardFileOptionJavaOuterClassnameContext ctx) {
verifyOptionNameUnique("javaOuterClassname", ctx.getStart());
scopes.getFileOptions().setJavaOuterClassname(removeQuotes(ctx.StringLiteral().getText()));
}
@Override
public void exitStandardFileOptionJavaMultipleFiles(
final StandardFileOptionJavaMultipleFilesContext ctx) {
verifyOptionNameUnique("javaMultipleFiles", ctx.getStart());
scopes.getFileOptions().setJavaMultipleFiles(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
@Override
public void exitStandardFileOptionCcGenericServices(
final StandardFileOptionCcGenericServicesContext ctx) {
verifyOptionNameUnique("ccGenericServices", ctx.getStart());
scopes.getFileOptions().setCcGenericServices(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
@Override
public void exitStandardFileOptionPyGenericServices(
final StandardFileOptionPyGenericServicesContext ctx) {
verifyOptionNameUnique("pyGenericServices", ctx.getStart());
scopes.getFileOptions().setPyGenericServices(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
@Override
public void exitStandardFileOptionJavaGenericServices(
final StandardFileOptionJavaGenericServicesContext ctx) {
verifyOptionNameUnique("javaGenericServices", ctx.getStart());
scopes.getFileOptions().setJavaGenericServices(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
@Override
public void exitStandardFileOptionJavaGenerateEqualsAndHash(
final StandardFileOptionJavaGenerateEqualsAndHashContext ctx) {
verifyOptionNameUnique("javaGenerateEqualsAndHash", ctx.getStart());
scopes.getFileOptions().setJavaGenerateEqualsAndHash(
Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
@Override
public void exitStandardFileOptionOptimizeFor(final StandardFileOptionOptimizeForContext ctx) {
verifyOptionNameUnique("optimizeFor", ctx.getStart());
scopes.getFileOptions().setOptimizeFor(OptimizeMode.valueOf(ctx.optimizeMode().getText()));
}
@Override
public void exitStandardFileOptionJavaStringCheckUtf8(
final StandardFileOptionJavaStringCheckUtf8Context ctx) {
verifyOptionNameUnique("javaStringCheckUtf8", ctx.getStart());
scopes.getFileOptions().setJavaStringCheckUtf8(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
@Override
public void exitStandardFileOptionDeprecated(final StandardFileOptionDeprecatedContext ctx) {
verifyOptionNameUnique("deprecated", ctx.getStart());
scopes.getFileOptions().setDeprecated(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
// **** Standard MessageOptions
@Override
public void exitStandardMessageOptionMessageSetWireFormat(
final StandardMessageOptionMessageSetWireFormatContext ctx) {
verifyOptionNameUnique("messageSetWireFormat", ctx.getStart());
scopes.getMessageOptions().setMessageSetWireFormat(
Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
@Override
public void exitStandardMessageOptionNoStandardDescriptorAccessor(
final StandardMessageOptionNoStandardDescriptorAccessorContext ctx) {
verifyOptionNameUnique("noStandardDescriptorAccessor", ctx.getStart());
scopes.getMessageOptions().setNoStandardDescriptorAccessor(
Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
@Override
public void exitStandardMessageOptionDeprecated(final StandardMessageOptionDeprecatedContext ctx) {
verifyOptionNameUnique("deprecated", ctx.getStart());
scopes.getMessageOptions().setDeprecated(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
// **** Standard ServiceOptions
@Override
public void exitStandardServiceOptionDeprecated(final StandardServiceOptionDeprecatedContext ctx) {
verifyOptionNameUnique("deprecated", ctx.getStart());
scopes.getServiceOptions().setDeprecated(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
// **** Standard MethodOptions
@Override
public void exitStandardMethodOptionDeprecated(final StandardMethodOptionDeprecatedContext ctx) {
verifyOptionNameUnique("deprecated", ctx.getStart());
scopes.getMethodOptions().setDeprecated(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
// **** Standard EnumOptions
@Override
public void exitStandardEnumOptionDeprecated(final StandardEnumOptionDeprecatedContext ctx) {
verifyOptionNameUnique("deprecated", ctx.getStart());
scopes.getEnumOptions().setDeprecated(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
@Override
public void exitStandardEnumOptionAllowAlias(final StandardEnumOptionAllowAliasContext ctx) {
verifyOptionNameUnique("allow_alias", ctx.getStart());
scopes.getEnumOptions().setAllowAlias(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
// **** Standard EnumValueOptions
@Override
public void exitStandardEnumValueOptionDeprecated(
final StandardEnumValueOptionDeprecatedContext ctx) {
verifyOptionNameUnique("deprecated", ctx.getStart());
scopes.getEnumValueOptions().setDeprecated(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
// field options
@Override
public void exitStandardFieldOptionCTypeOption(final StandardFieldOptionCTypeOptionContext ctx) {
verifyOptionNameUnique("ctype", ctx.getStart());
scopes.getFieldOptions().setCtype(FieldOptions.CType.valueOf(ctx.cType().getText()));
}
@Override
public void exitStandardFieldOptionExperimentalMapKey(
final StandardFieldOptionExperimentalMapKeyContext ctx) {
verifyOptionNameUnique("experimentalMapKey", ctx.getStart());
scopes.getFieldOptions().setExperimentalMapKey(removeQuotes(ctx.StringLiteral().getText()));
}
@Override
public void exitStandardFieldOptionLazy(final StandardFieldOptionLazyContext ctx) {
verifyOptionNameUnique("lazy", ctx.getStart());
scopes.getFieldOptions().setLazy(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
@Override
public void exitStandardFieldOptionDeprecated(final StandardFieldOptionDeprecatedContext ctx) {
verifyOptionNameUnique("deprecated", ctx.getStart());
scopes.getFieldOptions().setDeprecated(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
@Override
public void exitStandardFieldOptionWeak(final StandardFieldOptionWeakContext ctx) {
verifyOptionNameUnique("weak", ctx.getStart());
scopes.getFieldOptions().setWeak(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
@Override
public void exitStandardFieldOptionPacked(final StandardFieldOptionPackedContext ctx) {
verifyOptionNameUnique("packed", ctx.getStart());
scopes.getFieldOptions().setPacked(Boolean.valueOf(ctx.BooleanLiteral().getText()));
}
@Override
public void exitCustomOption(final CustomOptionContext ctx) {
verifyCustomOptionNameUnique(ctx);
final UninterpretedOption.Builder optionBuilder = scopes.addCustomOption();
for (final CustomOptionNamePartContext part : ctx.customOptionName().customOptionNamePart()) {
if (part.identifier() == null) {
optionBuilder.addNameBuilder().setNamePart(part.customOptionNamePartId().getText())
.setIsExtension(true);
} else {
optionBuilder.addNameBuilder().setNamePart(part.identifier().getText())
.setIsExtension(false);
}
}
final OptionAggregateValueContext optionAggregateValue =
ctx.customOptionValue().optionAggregateValue();
if (optionAggregateValue != null) {
// optionBuilder.setAggregateValue(optionAggregateValue.getText());
final String aggregateValue = getAggregateValue(optionAggregateValue);
optionBuilder.setAggregateValue(aggregateValue);
} else {
final OptionScalarValueContext optionScalarValue =
ctx.customOptionValue().optionScalarValue();
// TODO: 'inf', 'nan', and '\?' - deal with or not to deal with?
if (optionScalarValue.identifier() != null) {
optionBuilder.setIdentifierValue(optionScalarValue.identifier().getText());
} else if (optionScalarValue.BooleanLiteral() != null) {
optionBuilder.setStringValue(ByteString.copyFromUtf8(optionScalarValue.BooleanLiteral()
.getText()));
} else if (optionScalarValue.StringLiteral() != null) {
optionBuilder.setStringValue(ByteString.copyFromUtf8(removeQuotes(optionScalarValue
.StringLiteral().getText())));
} else if (optionScalarValue.IntegerLiteral() != null) {
// setCustomOptionIntValue(optionScalarValue.IntegerLiteral(), optionBuilder);
optionBuilder.setPositiveIntValue(PrimitiveTypesUtil.parseUInt64(optionScalarValue
.IntegerLiteral().getText()));
} else if (optionScalarValue.NegativeIntegerLiteral() != null) {
// setCustomOptionIntValue(optionScalarValue.NegativeIntegerLiteral(), optionBuilder);
optionBuilder.setNegativeIntValue(PrimitiveTypesUtil.parseInt64(optionScalarValue
.NegativeIntegerLiteral().getText()));
} else if (optionScalarValue.doubleValue() != null) {
optionBuilder.setDoubleValue(Double.parseDouble(optionScalarValue.doubleValue().getText()));
}
}
}
/**
* Verifies uniqueness for custom options with a single NamePart. The multipart custom options
* should be validated during UninterpetedOption resolution in OptionsBuilder, not here!
*/
private void verifyCustomOptionNameUnique(final CustomOptionContext ctx) {
if (ctx.customOptionName().customOptionNamePart().size() == 1) {
final CustomOptionNamePartContext namePart = ctx.customOptionName().customOptionNamePart(0);
verifyOptionNameUnique(namePart.getText(), namePart.getStart());
}
}
private String getAggregateValue(final OptionAggregateValueContext optionAggregateValue) {
final StringBuilder sb = new StringBuilder();
appendSpaceToAggregateField(sb, optionAggregateValue);
return sb.toString();
}
private void appendSpaceToAggregateField(final StringBuilder sb, final RuleNode ctx) {
for (int i = 0; i < ctx.getChildCount(); i++) {
final ParseTree child = ctx.getChild(i);
if (child instanceof RuleNode) {
appendSpaceToAggregateField(sb, (RuleNode) child);
if (child instanceof OptionAggregateValueFieldContext) {
sb.append(AGREGATE_VALUE_FIELD_DELIMITER);
}
} else {
sb.append(child.getText());
}
}
}
/*
* private StringBuilder appendDelimitedAggregateValue(final StringBuilder sb, final
* OptionAggregateValueContext optionAggregateValue) { sb.append("{");
*
* for (OptionAggregateValueFieldContext field : optionAggregateValue.optionAggregateValueField())
* { appendDelimitedAggregateValueField(sb, field).append(AGREGATE_VALUE_FIELD_DELIMITER); }
*
* return sb.append("}"); }
*
* private StringBuilder appendDelimitedAggregateValueField(final StringBuilder sb, final
* OptionAggregateValueFieldContext field) {
* sb.append(field.aggregateCustomOptionName().getText());
*
* final OptionAggregateValueContext optionAggregateValue = field.optionAggregateValue();
*
* if (optionAggregateValue != null) { appendDelimitedAggregateValue(sb, optionAggregateValue); }
* else { sb.append(":");
*
* final OptionAggregateListValueContext optionAggregateListValue = field
* .optionAggregateListValue();
*
* if (optionAggregateListValue != null) { appendAggregateValueList(sb, optionAggregateListValue);
* } }
*
* return sb; }
*
* private void appendAggregateValueList(final StringBuilder sb, final
* OptionAggregateListValueContext optionAggregateListValue) { sb.append("[");
*
* final List<OptionAggregateValueContext> aggregateValues = optionAggregateListValue
* .optionAggregateValue();
*
* if (!aggregateValues.isEmpty()) { for (OptionAggregateValueContext aggregateValue :
* aggregateValues) { appendDelimitedAggregateValue(sb, aggregateValue).append(","); }
*
* sb.deleteCharAt(sb.length() - 1); }
*
* sb.append("]"); }
*/
// ****************************** All Options END ************************************
// **** Messages
@Override
public void enterMessage(final MessageContext ctx) {
scopes.addMessage(ctx.identifier().getText());
}
@Override
public void exitMessage(final MessageContext ctx) {
scopes.popScope();
}
// Groups
@Override
public void enterGroup(final GroupContext ctx) {
scopes.addGroup(ctx.groupIdentifier().getText());
}
@Override
public void exitGroup(final GroupContext ctx) {
scopes.popScope();
final String groupName = ctx.groupIdentifier().getText();
// add group's field
final FieldDescriptorProto.Builder fieldBuilder = scopes.addField();
setFieldBuilder(fieldBuilder, groupName.toLowerCase(), ctx.fieldNumber().getText(),
ctx.label().getText(), FieldDescriptorProto.Type.TYPE_GROUP).setTypeName(groupName);
scopes.popScope();
contextLookup.addGroup(fieldBuilder, ctx);
scopes.addIfUnresolved(fieldBuilder);
scopes.verifyField(fieldBuilder);
}
// Extensions
@Override
public void exitExtensions(final ExtensionsContext ctx) {
final Integer extensionStart = Integer.decode(ctx.fieldNumber().getText());
final ExtensionRangeEndContext extensionRangeEnd = ctx.extensionRangeEnd();
final Integer extensionEnd;
if (extensionRangeEnd == null) {
extensionEnd = extensionStart;
} else {
final FieldNumberContext fieldNumber = extensionRangeEnd.fieldNumber();
extensionEnd = fieldNumber == null ? MAX_FIELD_NUMBER : Integer.decode(fieldNumber.getText());
}
scopes.addExtensionRange(extensionStart, extensionEnd, ctx);
}
// EnumStatements
@Override
public void enterEnumField(final EnumFieldContext ctx) {
scopes.addEnumValue();
}
@Override
public void exitEnumField(final EnumFieldContext ctx) {
final EnumValueDescriptorProto.Builder enumValueBuilder =
EnumValueDescriptorProto.Builder.class.cast(scopes.getProtoBuilder());
enumValueBuilder.setName(ctx.identifier().getText()).setNumber(
Integer.decode(ctx.enumValue().getText()));
scopes.popScope();
}
@Override
public void enterEnumStatement(final EnumStatementContext ctx) {
scopes.addEnum(ctx.identifier().getText());
}
@Override
public void exitEnumStatement(final EnumStatementContext ctx) {
scopes.popScope();
}
// Extends
@Override
public void enterExtend(final ExtendContext ctx) {
scopes.addExtend(ctx.extendedId().getText());
}
@Override
public void exitExtend(final ExtendContext ctx) {
scopes.popScope();
}
// Fields
@Override
public void enterField(final FieldContext ctx) {
scopes.addField();
}
@Override
public void exitField(final FieldContext ctx) {
final FieldDescriptorProto.Builder fieldBuilder =
FieldDescriptorProto.Builder.class.cast(scopes.getProtoBuilder());
final OptionalScalarFieldContext optionalScalarField = ctx.optionalScalarField();
if (optionalScalarField != null) {
final ParseTree primitiveField = optionalScalarField.getChild(0);
setScalarFieldBuilder(fieldBuilder, primitiveField.getChild(1).getText(), // name
primitiveField.getChild(3).getText(), // number
"optional", // label
primitiveField.getChild(0).getText()); // type
} else if (ctx.enumDefaultField() != null) {
final EnumDefaultFieldContext enumDefaultField = ctx.enumDefaultField();
setMessageFieldBuilder(fieldBuilder, enumDefaultField.identifier().getText(),
enumDefaultField.fieldNumber().getText(), "optional",
enumDefaultField.extendedId().getText()).setType(Type.TYPE_ENUM);
} else if (ctx.scalarType() != null) {
setScalarFieldBuilder(fieldBuilder, ctx.identifier().getText(), ctx.fieldNumber().getText(),
ctx.label().getText(), ctx.scalarType().getText());
} else { // this is a message or enum field
setMessageFieldBuilder(fieldBuilder, ctx.identifier().getText(), ctx.fieldNumber().getText(),
ctx.label().getText(), ctx.extendedId().getText());
}
scopes.popScope();
contextLookup.addField(fieldBuilder, ctx);
scopes.addIfUnresolved(fieldBuilder);
scopes.verifyField(fieldBuilder);
}
@Override
public void exitEnumDefaultFieldOption(final EnumDefaultFieldOptionContext ctx) {
setFieldDefaultValue(ctx);
}
@Override
public void exitSint64FieldOption(final Sint64FieldOptionContext ctx) {
setFieldDefaultValue(ctx);
}
@Override
public void exitSfixed64FieldOption(final Sfixed64FieldOptionContext ctx) {
setFieldDefaultValue(ctx);
}
@Override
public void exitSint32FieldOption(final Sint32FieldOptionContext ctx) {
setFieldDefaultValue(ctx);
}
@Override
public void exitBoolFieldOption(final BoolFieldOptionContext ctx) {
setFieldDefaultValue(ctx);
}
@Override
public void exitInt32FieldOption(final Int32FieldOptionContext ctx) {
setFieldDefaultValue(ctx);
}
@Override
public void exitSfixed32FieldOption(final Sfixed32FieldOptionContext ctx) {
setFieldDefaultValue(ctx);
}
@Override
public void exitDoubleFieldOption(final DoubleFieldOptionContext ctx) {
// TODO: what to do about 'inf' and 'nan'?
setFieldDefaultValue(ctx);
}
@Override
public void exitFloatFieldOption(final FloatFieldOptionContext ctx) {
// TODO: what to do about 'inf' and 'nan'?
setFieldDefaultValue(ctx);
}
@Override
public void exitFixed64FieldOption(final Fixed64FieldOptionContext ctx) {
setFieldDefaultValue(ctx);
}
@Override
public void exitFixed32FieldOption(final Fixed32FieldOptionContext ctx) {
setFieldDefaultValue(ctx);
}
@Override
public void exitUint32FieldOption(final Uint32FieldOptionContext ctx) {
setFieldDefaultValue(ctx);
}
@Override
public void exitUint64FieldOption(final Uint64FieldOptionContext ctx) {
setFieldDefaultValue(ctx);
}
@Override
public void exitBytesFieldOption(final BytesFieldOptionContext ctx) {
if (ctx.getChildCount() == 3) {
verifyOptionNameUnique("default", ctx.getStart());
final FieldDescriptorProto.Builder fieldBuilder =
FieldDescriptorProto.Builder.class.cast(scopes.getProtoBuilder());
final String input = ctx.getChild(2).getText();
try {
final String result =
protoEscapeBytes(unescapeBytes(input.substring(1, input.length() - 1)));
fieldBuilder.setDefaultValue(result);
} catch (final InvalidEscapeSequenceException e) {
contextLookup.reportInvalidDefaultValue((ParserRuleContext) ctx.getChild(2), e);
}
}
}
@Override
public void exitStringFieldOption(final StringFieldOptionContext ctx) {
// TODO: what to do about '\?'
if (ctx.getChildCount() == 3) {
verifyOptionNameUnique("default", ctx.getStart());
final FieldDescriptorProto.Builder fieldBuilder =
FieldDescriptorProto.Builder.class.cast(scopes.getProtoBuilder());
final String input = ctx.getChild(2).getText();
try {
final String result =
unescapeText(input.substring(1, input.length() - 1).replaceAll("\\\\[?]", "?"));
fieldBuilder.setDefaultValue(result);
} catch (final InvalidEscapeSequenceException e) {
contextLookup.reportInvalidDefaultValue((ParserRuleContext) ctx.getChild(2), e);
}
}
}
@Override
public void exitInt64FieldOption(final Int64FieldOptionContext ctx) {
setFieldDefaultValue(ctx);
}
private void setFieldDefaultValue(final ParserRuleContext ctx) {
if (ctx.getChildCount() == 3) {
verifyOptionNameUnique("default", ctx.getStart());
final FieldDescriptorProto.Builder fieldBuilder =
FieldDescriptorProto.Builder.class.cast(scopes.getProtoBuilder());
fieldBuilder.setDefaultValue(ctx.getChild(2).getText());
}
}
// services
@Override
public void enterService(final ServiceContext ctx) {
scopes.addService();
}
@Override
public void exitService(final ServiceContext ctx) {
final ServiceDescriptorProto.Builder serviceBuilder =
ServiceDescriptorProto.Builder.class.cast(scopes.getProtoBuilder());
serviceBuilder.setName(ctx.identifier().getText());
scopes.popScope();
}
// methods
@Override
public void enterMethodStatement(final MethodStatementContext ctx) {
scopes.addMethod();
}
@Override
public void exitMethodStatement(final MethodStatementContext ctx) {
final MethodDescriptorProto.Builder methodBuilder =
MethodDescriptorProto.Builder.class.cast(scopes.getProtoBuilder());
methodBuilder.setName(ctx.identifier().getText()).setInputType(ctx.extendedId(0).getText())
.setOutputType(ctx.extendedId(1).getText());
scopes.popScope();
}
// oneofs
@Override
public void enterOneofStatement(final OneofStatementContext ctx) {
scopes.addOneOf();
}
@Override
public void exitOneofStatement(final OneofStatementContext ctx) {
final OneofDescriptorProto.Builder oneofBuilder =
OneofDescriptorProto.Builder.class.cast(scopes.getProtoBuilder());
oneofBuilder.setName(ctx.identifier().getText());
scopes.popScope();
scopes.verifyOneofName(oneofBuilder);
}
// oneofFields
@Override
public void enterOneofField(final OneofFieldContext ctx) {
scopes.addField();
}
// TODO: reuse exitField and this - they differ very little
@Override
public void exitOneofField(final OneofFieldContext ctx) {
final FieldDescriptorProto.Builder fieldBuilder =
FieldDescriptorProto.Builder.class.cast(scopes.getProtoBuilder());
final OptionalScalarFieldContext optionalScalarField = ctx.optionalScalarField();
if (optionalScalarField != null) {
final ParseTree primitiveField = optionalScalarField.getChild(0);
setScalarFieldBuilder(fieldBuilder, primitiveField.getChild(1).getText(), // name
primitiveField.getChild(3).getText(), // number
"optional", // label
primitiveField.getChild(0).getText()); // type
} else if (ctx.enumDefaultField() != null) {
final EnumDefaultFieldContext enumDefaultField = ctx.enumDefaultField();
setMessageFieldBuilder(fieldBuilder, enumDefaultField.identifier().getText(),
enumDefaultField.fieldNumber().getText(), "optional",
enumDefaultField.extendedId().getText()).setType(Type.TYPE_ENUM);
} else if (ctx.scalarType() != null) {
setScalarFieldBuilder(fieldBuilder, ctx.identifier().getText(), ctx.fieldNumber().getText(),
"optional", ctx.scalarType().getText());
} else { // this is a message field
setMessageFieldBuilder(fieldBuilder, ctx.identifier().getText(), ctx.fieldNumber().getText(),
"optional", ctx.extendedId().getText());
}
scopes.popScope();
contextLookup.addField(fieldBuilder, ctx);
scopes.addIfUnresolved(fieldBuilder);
scopes.verifyField(fieldBuilder);
}
// oneofGroups
@Override
public void enterOneofGroup(final OneofGroupContext ctx) {
scopes.addGroup(ctx.groupIdentifier().getText());
}
@Override
public void exitOneofGroup(final OneofGroupContext ctx) {
scopes.popScope();
final String groupName = ctx.groupIdentifier().getText();
// add group's field
final FieldDescriptorProto.Builder fieldBuilder = scopes.addField();
setFieldBuilder(fieldBuilder, groupName.toLowerCase(), ctx.fieldNumber().getText(), "optional",
FieldDescriptorProto.Type.TYPE_GROUP).setTypeName(groupName);
scopes.popScope();
contextLookup.addGroup(fieldBuilder, ctx);
scopes.addIfUnresolved(fieldBuilder);
scopes.verifyField(fieldBuilder);
}
// **************** ParserUtils
private FieldDescriptorProto.Builder setMessageFieldBuilder(
final FieldDescriptorProto.Builder fieldBuilder, final String name, final String number,
final String label, final String typeName) {
return fieldBuilder.setName(name)
.setLabel(FieldDescriptorProto.Label.valueOf("LABEL_" + label.toUpperCase()))
.setNumber(Integer.decode(number))
// .setType(FieldDescriptorProto.Type.TYPE_MESSAGE) this could be ENUM or MESSAGE, so we
// don't set it!
.setTypeName(typeName);
}
private FieldDescriptorProto.Builder setScalarFieldBuilder(
final FieldDescriptorProto.Builder fieldBuilder, final String name, final String number,
final String label, final String type) {
return setFieldBuilder(fieldBuilder, name, number, label,
FieldDescriptorProto.Type.valueOf("TYPE_" + type.toUpperCase()));
}
private FieldDescriptorProto.Builder setFieldBuilder(
final FieldDescriptorProto.Builder fieldBuilder, final String name, final String number,
final String label, final FieldDescriptorProto.Type type) {
return fieldBuilder.setName(name)
.setLabel(FieldDescriptorProto.Label.valueOf("LABEL_" + label.toUpperCase()))
.setNumber(Integer.decode(number)).setType(type);
}
private String removeQuotes(final String text) {
return text.substring(1, text.length() - 1);
}
private void verifyOptionNameUnique(final String optionName, final Token optionNameToken) {
if (!scopes.isOptionNameUnique(optionName)) {
contextLookup.reportNonUniqueOptionNameError(optionName, optionNameToken);
}
}
// ContextLookup Stuff
static final class ContextLookup {
private final Map<Object, ParseTree> lookup;
private final IProtoErrorListener errorListener;
public ContextLookup(final IProtoErrorListener errorListener) {
lookup = new IdentityHashMap<Object, ParseTree>();
this.errorListener = errorListener;
}
public IProtoErrorListener getErrorListener() {
return errorListener;
}
public ParseTree addField(final FieldDescriptorProtoOrBuilder key, final FieldContext ctx) {
return lookup.put(key, ctx);
}
public ParseTree addField(final FieldDescriptorProtoOrBuilder key, final OneofFieldContext ctx) {
return lookup.put(key, ctx);
}
public ParseTree addGroup(final FieldDescriptorProtoOrBuilder key, final GroupContext ctx) {
return lookup.put(key, ctx);
}
public ParseTree addGroup(final FieldDescriptorProtoOrBuilder key, final OneofGroupContext ctx) {
return lookup.put(key, ctx);
}
public ParseTree addExtensionRange(final ExtensionRange.Builder key, final ExtensionsContext ctx) {
return lookup.put(key, ctx);
}
public ParseTree getContext(final Object key, final boolean removeMe) {
return removeMe ? lookup.remove(key) : lookup.get(key);
}
public void reportInvalidDefaultValue(final ParserRuleContext ctx, final Exception e) {
errorListener.validationError(ctx.getStart().getLine(), ctx.getStart()
.getCharPositionInLine(), e.getMessage(), new RuntimeException(e));
}
public void reportFieldInExtensionRangeEror(final FieldDescriptorProtoOrBuilder field,
final boolean removeMe) {
final ParseTree context = getContext(field, removeMe);
final Token token;
if (context instanceof GroupContext) {
token = getGroupNumberToken((GroupContext) context);
} else { // this is a regular field
token = getFieldNumberToken((FieldContext) context);
}
errorListener.validationError(token.getLine(), token.getCharPositionInLine(), null,
new FieldInExtensionRangeException(field.getName()));
}
public void reportInvalidExtensionRange(final InvalidExtensionRange exception,
final ExtensionsContext ctx) {
final Token token = ctx.getStart();
errorListener
.validationError(token.getLine(), token.getCharPositionInLine(), null, exception);
}
public void reportNonUniqueFieldNameError(final FieldDescriptorProtoOrBuilder field,
final boolean removeMe) {
final ParseTree context = getContext(field, removeMe);
final Token token;
if (context instanceof GroupContext) {
token = getGroupNameToken((GroupContext) context);
} else { // this is a regular field
token = getFieldNameToken((FieldContext) context);
}
errorListener.validationError(token.getLine(), token.getCharPositionInLine(), null,
new NonUniqueException(field.getName(), "field name"));
}
public void reportNonUniqueOneofNameError(final OneofDescriptorProtoOrBuilder oneof,
final boolean removeMe) {
final OneofStatementContext context = (OneofStatementContext) getContext(oneof, removeMe);
final Token token = context.identifier().getStart();
errorListener.validationError(token.getLine(), token.getCharPositionInLine(), null,
new NonUniqueException(oneof.getName(), "field name"));
}
public void reportNonUniqueFieldNumberError(final FieldDescriptorProtoOrBuilder field,
final boolean removeMe) {
final ParseTree context = getContext(field, removeMe);
final Token token;
if (context instanceof GroupContext) {
token = getGroupNumberToken((GroupContext) context);
} else { // this is a regular field
token = getFieldNumberToken((FieldContext) context);
}
errorListener.validationError(token.getLine(), token.getCharPositionInLine(), null,
new NonUniqueException(field.getName(), "field number"));
}
public void reportNonUniqueExtensionNumberError(final FieldDescriptorProtoOrBuilder field,
final boolean removeMe) {
final Token token = getFieldNumberToken((FieldContext) getContext(field, removeMe));
errorListener.validationError(token.getLine(), token.getCharPositionInLine(), null,
new NonUniqueExtensionNumber(field.getExtendee(), field.getName(), field.getNumber()));
}
public void reportUnresolvedTypeNameError(final FieldDescriptorProtoOrBuilder field,
final List<String> unresolvedInfo, final boolean removeMe) {
// final Token token = getFieldTypeNameToken((FieldContext) getContext(field, removeMe));
// report just a start of the field context, as there can be also the extendContext
// it would be overkill to cache extendContext just for this, though we could!
final Token token = ((FieldContext) getContext(field, removeMe)).getStart();
errorListener.validationError(token.getLine(), token.getCharPositionInLine(), null,
new UnresolvedTypeNameException(field.getName(), unresolvedInfo));
}
public void reportNonUniqueOptionNameError(final String optionName, final Token optionNameToken) {
errorListener.validationError(optionNameToken.getLine(), optionNameToken
.getCharPositionInLine(), null, new NonUniqueException(optionName, "option name"));
}
private Token getGroupNumberToken(final GroupContext ctx) {
return ctx.groupIdentifier().getStart();
}
private Token getGroupNameToken(final GroupContext ctx) {
return ctx.fieldNumber().getStart();
}
private Token getFieldNumberToken(final FieldContext ctx) {
if (ctx.fieldNumber() != null) {
return ctx.fieldNumber().getStart();
} else {
return ((FieldNumberContext) ctx.optionalScalarField().getChild(0).getChild(3)).getStart();
}
}
private Token getFieldNameToken(final FieldContext ctx) {
if (ctx.identifier() != null) {
return ctx.identifier().getStart();
} else {
return ((FieldNumberContext) ctx.optionalScalarField().getChild(0).getChild(1)).getStart();
}
}
public void putAllFields(final Map<FieldDescriptorProtoOrBuilder, ? extends ParseTree> fields) {
lookup.putAll(fields);
}
public int size() {
return lookup.size();
}
public boolean isEmpty() {
return lookup.isEmpty();
}
@Override
public boolean equals(final Object o) {
return lookup.equals(o);
}
@Override
public int hashCode() {
return lookup.hashCode();
}
}
public static final class ParsedContext {
private static final ParsedContext DEFAULT_DESCRIPTOR_CONTEXT = new ParsedContext();
private FileDescriptorProtoOrBuilder proto;
private final List<Map.Entry<FieldDescriptorProto.Builder, FieldContext>> unresolved;
private ParsedContext() {
proto = DescriptorProtos.getDescriptor().toProto();
unresolved = Collections.emptyList();
}
ParsedContext(final FileDescriptorProto proto) {
this.proto = proto;
unresolved = Collections.emptyList();
}
private ParsedContext(final FileDescriptorProto.Builder proto,
final List<FieldDescriptorProto.Builder> unresolved, final ContextLookup lookup) {
this.proto = proto;
if ((unresolved == null) || unresolved.isEmpty()) {
this.unresolved = Collections.emptyList();
} else {
if (lookup == null) {
throw new NullPointerException();
}
this.unresolved = new ArrayList<Map.Entry<FieldDescriptorProto.Builder, FieldContext>>();
for (final FieldDescriptorProto.Builder field : unresolved) {
final FieldContext context = (FieldContext) lookup.getContext(field, false);
if (context == null) {
throw new IllegalStateException("field context must not be null");
}
this.unresolved.add(new SimpleEntry<FieldDescriptorProto.Builder, FieldContext>(field,
context));
}
}
}
public static ParsedContext getDescriptorProtoParsedContext() {
return DEFAULT_DESCRIPTOR_CONTEXT;
}
public FileDescriptorProtoOrBuilder getProto() {
return proto;
}
public boolean isBuilt() {
return proto instanceof FileDescriptorProto;
}
public boolean resolveAllRefs(final Collection<FileDescriptor> dependencies,
final IProtoErrorListener errorListener) {
if (isBuilt()) {
throw new IllegalStateException("not supported when proto is already built");
}
final Map<String, NameContext> cache = new HashMap<String, NameContext>();
if (resolveAllRefs(dependencies, errorListener, cache)) {
return true;
}
for (final Entry<FieldDescriptorProto.Builder, FieldContext> entry : unresolved) {
reportUnresolvedTypeNameError(entry.getKey(), entry.getValue(), errorListener);
}
return false;
}
private boolean resolveAllRefs(final Collection<FileDescriptor> dependencies,
final IProtoErrorListener errorListener, final Map<String, NameContext> cache) {
if (unresolved.isEmpty()) {
proto = ((FileDescriptorProto.Builder) proto).build();
return true;
} else if (dependencies.isEmpty()) {
return false;
}
// resolve private dependencies first
for (final FileDescriptor dependency : dependencies) {
// FIXME check with protoc that only exact package names are searchable
if (proto.getPackage().equals(dependency.getPackage())) {
for (final Iterator<Entry<FieldDescriptorProto.Builder, FieldContext>> iterator =
unresolved.iterator(); iterator.hasNext();) {
final Entry<FieldDescriptorProto.Builder, FieldContext> entry = iterator.next();
if (resolveField(entry.getKey(), entry.getValue(), dependency, cache)) {
iterator.remove();
}
}
if (unresolved.isEmpty()) {
proto = ((FileDescriptorProto.Builder) proto).build();
return true;
}
}
}
// now try public dependencies; they are transitive!
for (final FileDescriptor dependency : dependencies) {
final List<FileDescriptor> publicDependencies = dependency.getPublicDependencies();
if (!publicDependencies.isEmpty()) {
if (resolveAllRefs(publicDependencies, errorListener, cache)) {
return true;
}
}
}
return false;
}
private void buildProto() {
proto = ((FileDescriptorProto.Builder) proto).build();
}
private boolean resolveField(final FieldDescriptorProto.Builder field,
final FieldContext fieldContext, final FileDescriptor dependency,
final Map<String, NameContext> cache) {
boolean isResolved = true;
if (field.hasExtendee() && !field.getExtendee().startsWith(".")) {
final NameContext nameContext = resolveName(field.getExtendee(), dependency, cache);
if (nameContext.isEmpty()) {
isResolved = false;
} else {
field.setExtendee(nameContext.getName());
}
}
if (field.hasTypeName() && !field.getTypeName().startsWith(".")) {
final NameContext nameContext = resolveName(field.getTypeName(), dependency, cache);
if (nameContext.isEmpty()) {
isResolved = false;
} else {
field.setTypeName(nameContext.getName());
if (nameContext.isLeaf()) {
field.setType(Type.TYPE_ENUM);
} else {
field.setType(Type.TYPE_MESSAGE);
}
}
}
return isResolved;
}
private NameContext resolveName(final String name, final FileDescriptor fileProto,
final Map<String, NameContext> cache) {
NameContext nameContext = cache.get(name);
if (nameContext != null) {
return nameContext;
}
final EnumDescriptor enumDesc = fileProto.findEnumTypeByName(name);
if (enumDesc != null) {
nameContext = NameContext.newResolvedInstance(enumDesc.getFullName(), true);
} else {
final Descriptor descriptor = fileProto.findMessageTypeByName(name);
if (descriptor != null) {
nameContext = NameContext.newResolvedInstance(descriptor.getFullName(), false);
}
}
if ((nameContext == null) && !fileProto.getMessageTypes().isEmpty()) {
nameContext = resolveName(name, fileProto.getMessageTypes());
}
if (nameContext == null) {
nameContext = NameContext.emptyInstance();
}
cache.put(name, nameContext);
return nameContext;
}
private NameContext resolveName(final String name, final List<Descriptor> messageTypes) {
for (final Descriptor descriptor : messageTypes) {
final EnumDescriptor enumDesc = descriptor.findEnumTypeByName(name);
if (enumDesc != null) {
return NameContext.newResolvedInstance(enumDesc.getFullName(), true);
}
final Descriptor messageDesc = descriptor.findNestedTypeByName(name);
if (messageDesc != null) {
return NameContext.newResolvedInstance(messageDesc.getFullName(), false);
}
if (descriptor.getNestedTypes().isEmpty()) {
continue;
}
final NameContext nameContext = resolveName(name, descriptor.getNestedTypes());
if (nameContext != null) {
return nameContext;
}
}
return null;
}
private List<String> getPublicDependencies(final FileDescriptorProtoOrBuilder fileProto) {
if (fileProto.getPublicDependencyCount() == 0) {
return Collections.emptyList();
}
final List<String> publicDependencies =
new ArrayList<String>(fileProto.getPublicDependencyCount());
for (final Integer index : fileProto.getPublicDependencyList()) {
publicDependencies.add(fileProto.getDependency(index));
}
return publicDependencies;
}
private void reportUnresolvedTypeNameError(final FieldDescriptorProtoOrBuilder field,
final FieldContext fieldContext, final IProtoErrorListener errorListener) {
final Token token = fieldContext.getStart();
errorListener.validationError(token.getLine(), token.getCharPositionInLine(), null,
new UnresolvedTypeNameException(field.getName(), getUnresolvedInfo(field)));
}
// FIXME combine with Symbol.getUnresolvedInfo()?
private List<String> getUnresolvedInfo(final FieldDescriptorProtoOrBuilder field) {
final List<String> unresolvedProps = new ArrayList<String>();
if (field.hasExtendee() && !field.getExtendee().startsWith(".")) {
unresolvedProps.add("field " + field.getName() + "'s extendee property: '"
+ field.getExtendee() + "'");
}
if (field.hasTypeName() && !field.getTypeName().startsWith(".")) {
unresolvedProps.add("field " + field.getName() + "'s typeName property: '"
+ field.getTypeName() + "'");
}
return unresolvedProps;
}
}
}